package com.ku.log.support.aspect;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.ku.log.entity.Operator;
import com.ku.log.entity.SysLog;
import com.ku.log.facade.OperatorAvailable;
import com.ku.log.service.LogService;
import com.ku.log.support.annotation.OperationLog;
import com.ku.log.support.constant.LoginAndLogout;
import com.ku.log.support.constant.ResultConstant;
import com.ku.log.util.OperatorCache;
import com.ku.log.util.ThreadLocals;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.swing.text.Style;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: GZQ
 * @create: 2020/08/09
 * @description: 日志切面，用户在方法执行前后获取日志信息，需配合OperationLog使用
 **/
@Aspect
@Component
@Slf4j
@Order(-6)
public class SystemLogAspect {

    public SystemLogAspect() {
        log.info("操作日志切面创建..");
    }

    @Autowired
    private HttpServletRequest request;
    @Autowired(required = false)
    private OperatorAvailable operatorAvailable;
    @Autowired
    private LogService logService;

    @Pointcut("@annotation(com.ku.log.support.annotation.OperationLog)")
    public void executeAspect(){};

    @Before(value = "executeAspect()")
    public void doBefore(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        OperationLog operationLog = method.getAnnotation(OperationLog.class);
        if (operationLog != null){
            SysLog sysLog = null;
            try {
                //获取注解上的各个属性
                String type = operationLog.type();
                String item = operationLog.item();
                String module = operationLog.module();
                sysLog = initLog(type,item,module);
                log.info("before线程: " + Thread.currentThread().getName() + "，type：" + type + "，item：" + item);
                Map<String, Object> paramMap = getParamMap(joinPoint.getArgs(), methodSignature.getParameterNames());

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @After(value = "executeAspect()")
    public void doAfter() {
        String token = request.getHeader("token");
        log.info("拿到请求token为：" + token);
        boolean isLogout = false;
        try {
            SysLog sysLog = ThreadLocals.getLocal(SysLog.class);
            if (sysLog == null) return;
            String operateType = sysLog.getOperateType();
            isLogout = LoginAndLogout.LOGOUT.equals(operateType);
            String item = sysLog.getItem();
            sysLog.setTitle(operateType + item);
            Operator operator = getOperator(token, operateType);
            sysLog.setOperator(operator);
            // 描述信息补全操作人
            String operateDesc = sysLog.getOperateDesc() == null ? "" : sysLog.getOperateDesc();
            sysLog.setOperateDesc(operator.getOperatorName() + sysLog.getTitle() + operateDesc);
        } catch (Exception e) {
            log.info("操作日志后置拦截处理异常：" + e.getMessage());
        } finally {
            if (isLogout) {
                OperatorCache.removeCache(token);
            }
        }
    }

    @AfterReturning(value = "executeAspect()")
    public void doAfterReturning() {
        log.info("doAfterReturning...");
        try {
            logService.addLog(ThreadLocals.getLocal(SysLog.class));
        } catch (Exception e) {
            log.info("doAfterReturning执行异常: " + e.getMessage());
        } finally {
            ThreadLocals.removeLocal(SysLog.class);
            ThreadLocals.removeLocal(Operator.class);
        }
    }

    /**异常情况更新操作结果为异常*/
    @AfterThrowing(pointcut = "executeAspect()",throwing="t")
    public void afterThrowing(Throwable t) throws Throwable{
        log.info("afterThrowing...");
        try {
            SysLog localLog = ThreadLocals.getLocal(SysLog.class);
            if (null != localLog) {
                String exception = t.toString();
                localLog.setResult(ResultConstant.ERROR);
                localLog.setOperateException(exception.length() > 500 ? exception.substring(0,500) : exception);
                logService.addLog(localLog);
            }
        } catch (Exception e) {
            log.info("afterThrowing 执行异常" + t.getMessage());
        } finally {
            ThreadLocals.removeLocal(SysLog.class);
            ThreadLocals.removeLocal(Operator.class);
        }
    }

    private SysLog initLog(String type, String item, String module) {
        return SysLog.builder().item(item).operateException("").operateTime(new Date())
                .method(request.getMethod()).uri(request.getRequestURI()).remoteIp(getRealIp(request))
                .result(ResultConstant.SUCCESS).operateType(type).module(module).build();
    }


    // 本地线程变量中获取当前线程所属操作人
    private Operator getOperator(String token, String operateType) {
        Operator operator;
        try {
            if (LoginAndLogout.LOGIN.equalsIgnoreCase(operateType)
                    && (operator = ThreadLocals.getLocal(Operator.class)) != null) {
                OperatorCache.setCache(operator.getToken(), operator);
                return operator;
            }
            if ((operator = OperatorCache.getCache(token)) != null) {
                return operator;
            }
            if ((operator = operatorAvailable.getOperator(token)) != null) {
                OperatorCache.setCache(token, operator);
                return operator;
            }
        } catch (Exception e) {
            log.info("获取当前操作人失败：" + e.getMessage());
        }
        return OperatorCache.anonymous;
    }

    // 记录请求参数
    private Map<String, Object> getParamMap(Object[] args, String[] parameterNames) {
        Map<String, Object> paramMap = new HashMap<>();
        if (args != null && args.length > 0) {
            for (int index = 0; index < args.length; index++) {
                Object arg = args[index];
                if (arg instanceof HttpServletRequest) {
                    continue;
                }
                if (arg instanceof MultipartFile) {
                    arg = ((MultipartFile) arg).getOriginalFilename();
                }
                String name = parameterNames[index];

                if (arg != null) {
                    log.info("arg 类型：" + arg.getClass());
                    String str = checkOrTransferJson(arg);
                    arg = str.length() > 1300 ? str.substring(0, 1300) : str;
                }
                paramMap.put(name, arg);
            }
        }
        return paramMap;
    }

    private String checkOrTransferJson(Object arg) {
        String jsonValue = JSONObject.toJSONString(arg);
        try {
            JSONObject.parseObject(jsonValue);
            return jsonValue;
        } catch (JSONException e){
            log.info("param can not transfer to json");
        }
        return arg.toString();
    }


    private String getRealIp(HttpServletRequest request) {
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String s = headerNames.nextElement();
            String header = request.getHeader(s);
            log.info("请求头: " + s + "-" + header);
        }
        log.info("request.getRemoteAddr(): " + request.getRemoteAddr());
        //X-Forwarded-For：Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");
        String unknown = "unknown";
        if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
            //Proxy-Client-IP：apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
            //WL-Proxy-Client-IP：weblogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
            //HTTP_CLIENT_IP：有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
            //X-Real-IP：nginx服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }
        String ip = null;
        //有些网络通过多层代理，那么获取到的ip就会有多个，一般都是通过逗号（,）分割开来，并且第一个ip为客户端的真实IP
        if (ipAddresses != null && ipAddresses.length() != 0) {
            ip = ipAddresses.split(",")[0];
        }

        //还是不能获取到，最后再通过request.getRemoteAddr();获取
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }
        log.info("找到ip为: " + ip);
        return ip;

    }
}
